/* Copyright (c) 2010 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.orkut.client.api.samples.greetings.server;

import com.google.appengine.api.urlfetch.FetchOptions;
import com.google.appengine.api.urlfetch.HTTPHeader;
import com.google.appengine.api.urlfetch.HTTPMethod;
import com.google.appengine.api.urlfetch.HTTPRequest;
import com.google.appengine.api.urlfetch.HTTPResponse;
import com.google.appengine.api.urlfetch.URLFetchService;
import com.google.gdata.util.common.base.Pair;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import com.google.orkut.client.api.BatchTransaction;
import com.google.orkut.client.api.Transaction;
import com.google.orkut.client.api.samples.greetings.server.db.GreetingsDb;
import com.google.orkut.client.api.samples.greetings.shared.AuthorizationException;
import com.google.orkut.client.api.samples.greetings.shared.CommonConstants;
import com.google.orkut.client.config.Config;
import com.google.orkut.client.transport.HttpRequest;
import com.google.orkut.client.transport.HttpRequestFactory;

import oauth.signpost.OAuthConsumer;
import oauth.signpost.exception.OAuthCommunicationException;
import oauth.signpost.exception.OAuthExpectationFailedException;
import oauth.signpost.exception.OAuthMessageSignerException;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;

import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collection;
import java.util.Map;

/**
 * An oauth authorized transport which is equivalent of "Transport"
 * class in the orkut-os-client sample app.
 *
 * @author Shishir Birmiwal
 */
public class AuthorizedChannel {

  private static final double FETCH_DEADLINE = 20.0;
  private final HttpRequestFactory httpFactory;
  private final Config config;
  private BatchTransaction batchTransaction;
  private final Pair<String,String> credentials;
  private final OAuthConsumer oauthConsumer;
  private final URLFetchService urlFetchService;
  private final HttpServletRequest request;
  private final PostTokenServices postTokenServices;

  public interface Factory {
    AuthorizedChannel create(String postToken, HttpServletRequest request)
    throws AuthorizationException;
  }

  @Inject
  public AuthorizedChannel(
      @Assisted HttpServletRequest request,
      @Assisted String postToken,
      PostTokenServices postTokenServices,
      GreetingsDb db,
      HttpRequestFactory httpFactory,
      Config config,
      OAuthConsumer oauthConsumer,
      URLFetchService urlFetchService) throws AuthorizationException {
    this.request = request;
    this.postTokenServices = postTokenServices;
    this.httpFactory = httpFactory;
    this.config = config;
    this.oauthConsumer = oauthConsumer;
    this.urlFetchService = urlFetchService;
    verifyCredentials(postToken);
    credentials = db.getCredentials(getSessionToken());
    batchTransaction = getNewBatchTransaction();
  }

  private String getSessionToken() {
    Cookie[] cookies = request.getCookies();
    if (cookies == null) {
      return null;
    }

    for (Cookie cookie : cookies) {
      if (cookie.getName().equals(CommonConstants.COOKIE)) {
        return cookie.getValue();
      }
    }
    return null;
  }

  private void verifyCredentials(String postToken) throws AuthorizationException {
    if (!postTokenServices.verifyRequest(request, postToken)) {
      throw new AuthorizationException();
    }
  }

  AuthorizedChannel add(Transaction transaction) {
    batchTransaction.add(transaction);
    return this;
  }

  public void run()
  throws IOException, OAuthMessageSignerException, OAuthExpectationFailedException, OAuthCommunicationException {
    HttpRequest request = batchTransaction.build();
    oauthConsumer.setTokenWithSecret(credentials.first, credentials.second);
    HttpRequestAdapter requestAdapter = new HttpRequestAdapter(request);
    oauthConsumer.sign(requestAdapter);

    HTTPRequest httpRequest = getHttpRequest(request);
    HTTPResponse response = urlFetchService.fetch(httpRequest);
    String responseString = new String(response.getContent(), "UTF-8");
    batchTransaction.setResponse(responseString);
    batchTransaction = getNewBatchTransaction();
  }

  public static class AppEngineRequestAdapter implements oauth.signpost.http.HttpRequest {

    private final String url;
    private final HTTPRequest request;

    public AppEngineRequestAdapter(String url) throws MalformedURLException {
      this.url = url;
      request = new HTTPRequest(new URL(url), HTTPMethod.GET, FetchOptions.Builder.withDeadline(FETCH_DEADLINE));
    }

    @Override
    public Map<String, String> getAllHeaders() {
      return null;
    }

    @Override
    public String getContentType() {
      return getHeader("Content-Type");
    }

    @Override
    public String getHeader(String arg0) {
      for (HTTPHeader header : request.getHeaders()) {
        if (header.getName().equals(arg0)) {
          return header.getValue();
        }
      }
      return null;
    }

    @Override
    public InputStream getMessagePayload() throws IOException {
      return null;
    }

    @Override
    public String getMethod() {
      return request.getMethod().toString();
    }

    @Override
    public String getRequestUrl() {
      return request.getURL().toString();
    }

    @Override
    public void setHeader(String arg0, String arg1) {
      request.addHeader(new HTTPHeader(arg0, arg1));
    }

    @Override
    public void setRequestUrl(String arg0) {
      throw new RuntimeException("wtf!");
    }

    HTTPRequest getHTTPRequest() {
      return request;
    }
  }

  public HTTPResponse fetchUrl(String url) throws IOException {
    oauthConsumer.setTokenWithSecret(credentials.first, credentials.second);
    AppEngineRequestAdapter httpRequest = new AppEngineRequestAdapter(url);
    try {
      oauthConsumer.sign(httpRequest);
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
    // sandbox.orkut.com
//    httpRequest.setHeader("Host", "www.orkut.com");
    HTTPRequest request = httpRequest.getHTTPRequest();
    HTTPResponse response = urlFetchService.fetch(request);
    return response;
  }

  private HTTPRequest getHttpRequest(oauth.signpost.http.HttpRequest httpRequest) throws MalformedURLException {
    HTTPRequest request = new HTTPRequest(new URL(httpRequest.getRequestUrl()), HTTPMethod.GET, FetchOptions.Builder.withDeadline(FETCH_DEADLINE));
    Map<String, String> allHeaders = httpRequest.getAllHeaders();
    for (Map.Entry<String, String> header : allHeaders.entrySet()) {
      request.addHeader(new HTTPHeader(header.getKey(), header.getValue()));
    }
    return request;
  }

  private HTTPRequest getHttpRequest(HttpRequest request)
      throws MalformedURLException {
    URL url = new URL(HttpRequestAdapter.getUrl(request));
    HTTPMethod method = HTTPMethod.valueOf(request.getMethod());
    HTTPRequest httpRequest = new HTTPRequest(url, method, FetchOptions.Builder.withDeadline(FETCH_DEADLINE));
    Collection<HttpRequest.Header> headers = request.getHeaders();
    for (HttpRequest.Header header : headers) {
      httpRequest.addHeader(new HTTPHeader(header.getName(), header.getValue()));
    }
    httpRequest.setPayload(request.getRequestBody());
    return httpRequest;
  }

  private BatchTransaction getNewBatchTransaction() {
    return new BatchTransaction(httpFactory, config);
  }
}
